from GameObjects import *
import textwrap
import libtcodpy as libtcod
from message import *
from Gui_Constants import *

#TODO: http://roguebasin.roguelikedevelopment.org/index.php?title=Complete_Roguelike_Tutorial,_using_python%2Blibtcod,_part_6
LIMIT_FPS     = 20  # 20 frames per second maximum

# Size of the map
MAP_WIDTH  = 80  # Size of the dungeon -- width
MAP_HEIGHT = 43  # Size of the dungeon -- height
INVENTORY_WIDTH = 50

# Initial location of the player
PLAYER_X = 25
PLAYER_Y = 23

# Room constraints
ROOM_MAX_SIZE     = 10
ROOM_MIN_SIZE     = 6
MAX_ROOMS         = 30
MAX_ROOM_MONSTERS = 3
MAX_ROOM_ITEMS    = 2

# Field of View stuff
FOV_ALGO = 0
FOV_LIGHT_WALLS = True
TORCH_RADIUS = 10

HEAL_AMOUNT = 4

#colors
colorDarkWall = libtcod.Color(0, 0, 100)
colorLightWall = libtcod.Color(130, 110, 50)
colorDarkGround = libtcod.Color(50, 50, 150)
colorLightGround = libtcod.Color(200, 180, 50)

# Global Variables
# Initialize the first turn to true
isFirstTurn = True
fov_recompute = True
game_state = 'playing'
player_action = None
mouse = libtcod.Mouse()
key = libtcod.Key()
inventory = []

###########################
# Functions for the game  #
###########################

##
# handleKeys()
# Description: A listener/handler for key presses
#
# Note: console_check_for_keypress() is used to find keys as if they are being typed
#       console_is_key_pressed() is used to find when keys are pressed
##
def handleKeys():
    # get the global variables
    global objects, fov_recompute, game_state, key, fov_map


    # Alt+Enter: toggle fullscreen
    if key.vk == libtcod.KEY_ENTER and key.lalt:
        libtcod.console_set_fullscreen(not libtcod.console_is_fullscreen())
    # Exit game
    elif key.vk == libtcod.KEY_ESCAPE:
        return 'exit'

    if game_state == 'playing':
        #Movement Keys
        # Up key
        if libtcod.console_is_key_pressed(libtcod.KEY_UP):
            player_move_or_attack(0, -1)
        # Down key
        elif libtcod.console_is_key_pressed(libtcod.KEY_DOWN):
            player_move_or_attack(0, 1)
        # Left Key
        elif libtcod.console_is_key_pressed(libtcod.KEY_LEFT):
            player_move_or_attack(-1, 0)
        # Right
        elif libtcod.console_is_key_pressed(libtcod.KEY_RIGHT):
            player_move_or_attack(1, 0)
        else:
            key_char = chr(key.c)

            if key_char == 'g':
                # pick up an item
                for object in objects:
                    if object.x == player.x and object.y == player.y and object.item:
                        object.item.pick_up(inventory, objects)
                        break
            elif key_char == 'i':
                # show the inventory
                chosen_item = inventory_menu('Press the key next to an item to use it, or any other to cancel.\n')
                if chosen_item is not None:
                    chosen_item.use(inventory)
            return 'didnt-take-turn'

##
# renderAll()
# Desccription: Draw the map and all objects on the list
##
def renderAll():
    global fov_recompute
    if fov_recompute:
        fov_recompute = False
        libtcod.map_compute_fov(fov_map, player.x, player.y, TORCH_RADIUS, FOV_LIGHT_WALLS, FOV_ALGO)

    for obj in objects:
        obj.draw(con, fov_map)

    # Draw the map
        for y in range(MAP_HEIGHT):
            for x in range(MAP_WIDTH):
                visible = libtcod.map_is_in_fov(fov_map, x, y)
                wall = map[x][y].blockedSight

                if not visible:
                    if map[x][y].explored:
                        # Draw the walls
                        if wall:
                            libtcod.console_set_char_background(con, x, y, colorDarkWall, libtcod.BKGND_SET)
                        # Draw the ground
                        else:
                            libtcod.console_set_char_background(con, x, y, colorDarkGround, libtcod.BKGND_SET)
                else:
                    if wall:
                        libtcod.console_set_char_background(con, x, y, colorLightWall, libtcod.BKGND_SET)
                    # Draw the ground
                    else:
                        libtcod.console_set_char_background(con, x, y, colorLightGround, libtcod.BKGND_SET)
                    map[x][y].explored = True

    # Place the console buffer over the screen
    libtcod.console_blit(con, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0, 0)

    # Prepare to render the GUI
    libtcod.console_set_default_background(panel, libtcod.darkest_grey)
    libtcod.console_clear(panel)

    # Print the game messages
    y = 1
    for (line, color) in game_msgs:
        libtcod.console_set_default_foreground(panel, color)
        libtcod.console_print_ex(panel, MSG_X, y, libtcod.BKGND_NONE, libtcod.LEFT, line)
        y += 1

    # Print the player's stats
    render_bar(1, 1, BAR_WIDTH, 'HP', player.fighter.hp, player.fighter.max_hp,
               libtcod.light_red, libtcod.darker_red)

    # Display the names of objects under the mouse
    libtcod.console_set_default_foreground(panel, libtcod.light_gray)
    libtcod.console_print_ex(panel, 1, 0, libtcod.BKGND_NONE, libtcod.LEFT, get_names_under_mouse())

    # applyTo the contents of panel to the root console
    libtcod.console_blit(panel, 0, 0, SCREEN_WIDTH, PANEL_HEIGHT, 0, 0, PANEL_Y)
##
# makeMap()
# Description: create the map using Tiles
##
def makeMap():
    global map
    # Fill the map with unblocked Tiles
    map = [[Tile(True)
            for y in range(MAP_HEIGHT) ]
                for x in range(MAP_WIDTH) ]

    # The rooms in our dungeon
    rooms = []
    num_rooms = 0

    for r in range(MAX_ROOMS):
        w = libtcod.random_get_int(0, ROOM_MIN_SIZE, ROOM_MAX_SIZE)
        h = libtcod.random_get_int(0, ROOM_MIN_SIZE, ROOM_MAX_SIZE)

        x = libtcod.random_get_int(0, 0, MAP_WIDTH - w - 1)
        y = libtcod.random_get_int(0, 0, MAP_HEIGHT - h - 1)

        new_room = Rect(x, y, w, h)

        # See if other rooms intersect with this one
        isBadRoom = False

        for other_room in rooms:
            if new_room.isIntersecting(other_room):
                isBadRoom = True
                break

        if not isBadRoom:
            createRoom(new_room)

            (new_x, new_y) = new_room.center()

            if num_rooms == 0:

                player.x = new_x
                player.y = new_y
                map[new_x][new_y].add_object(player)

                wood = Object(new_x+2, new_y, '=', 'firepit', libtcod.azure, blocks=False)
                wood.attributes[fire.ATRIBUTE_FLAMABLE_KEY] = fire.attribute_flammable(wood, is_on_fire=True)
                objects.append(wood)
                map[new_x+2][new_y].add_object(wood)

            else:
                (prev_x, prev_y) = rooms[num_rooms - 1].center()
                if libtcod.random_get_int(0, 0, 1) == 1:
                    createHTunnel(prev_x, new_x, prev_y)
                    createVTunnel(prev_y, new_y, new_x)
                else:
                    createVTunnel(prev_y, new_y, new_x)
                    createHTunnel(prev_x, new_x, prev_y)
        rooms.append(new_room)
        # populate room
        populate_rooms(new_room)
        num_rooms += 1



def createRoom(room):
    global map
    for x in range(room.x1 + 1, room.x2):
        for y in range(room.y1 + 1, room.y2):
            map[x][y].blocked = False
            map[x][y].blockedSight = False

def createHTunnel(x1, x2, y):
    global map
    for x in range(min(x1, x2), max(x1, x2) + 1):
        map[x][y].blocked = False
        map[x][y].blockedSight = False

def createVTunnel(y1, y2, x):
    global map
    for y in range(min(y1, y2), max(y1, y2) + 1):
        map[x][y].blocked = False
        map[x][y].blockedSight = False

def populate_rooms(room):
    global map

    num_monsters = libtcod.random_get_int(0, 0, MAX_ROOM_MONSTERS)
    monster = None

    for i in  range(num_monsters):
        x = libtcod.random_get_int(0, room.x1+1, room.x2-1)
        y = libtcod.random_get_int(0, room.y1+1, room.y2-1)
        if not is_blocked(x, y):
            if libtcod.random_get_int(0, 0, 100) < 80: # 80% chance of an orc
                fighter_component = Fighter(hp = 10, defense = 0, power = 3, death_function=monster_death)

                ai_component = BasicMonster()
                ai_component = SmartMonster()
                monster = Object(x, y, 'c', 'Cultist', libtcod.desaturated_green, True, fighter=fighter_component, ai=ai_component)

                monster.attributes["fighter"] = fighter_component

            else: # create a troll
                fighter_component = Fighter(hp = 16, defense = 1, power = 4, death_function=monster_death)
                #ai_component = BasicMonster()
                ai_component = SmartMonster()
                monster = Object(x, y, 'S', 'Shoggoth', libtcod.darker_green, True, fighter=fighter_component, ai = ai_component)

                monster.attributes["fighter"] = fighter_component

            map[x][y].add_object(monster)

            monster.attributes[fire.ATRIBUTE_FLAMABLE_KEY] = fire.attribute_flammable(monster, flame_resistance=20)
            objects.append(monster)

    # Place some random items
    num_items = libtcod.random_get_int(0, 0, MAX_ROOM_ITEMS)

    for i in range(num_items):
        # choose a random pot for the item
        x = libtcod.random_get_int(0, room.x1+1, room.x2 -1)
        y = libtcod.random_get_int(0, room.y1+1, room.y2 -1)

        # only place it if the tile is not blocked
        if not is_blocked(x, y):
            # create a healing potion
            item_component = Item(use_function=cast_heal)
            item = Object(x, y, '!', 'Health Potion', libtcod.violet, item=item_component)

            objects.append(item)
            send_to_back(item)

def is_blocked(x, y):
    # is the map tile available?
    if map[x][y].blocked:
        return True
    # is their an object on the tile?
    for object in map[x][y].stack:
        if object.blocks:
            return True
    return False

def player_move_or_attack(dx, dy):
    global fov_recompute, player, objects, map

    x = player.x + dx
    y = player.y + dy

    target = None
    for object in objects:
        if object.fighter and object.x == x and object.y == y:
            target = object
            break

    if target is not None:
        player.fighter.attack(target)
    else:
        player.move(map, objects, dx, dy)
        fov_recompute = True

def player_death(player):
    global game_state
    message('You died!', libtcod.dark_red)
    game_state = 'dead'

    player.char = '%'
    player.color = libtcod.dark_red

def monster_death(monster):
    message(monster.name.capitalize() + ' is dead', libtcod.white)
    monster.char = '%'
    monster.color = libtcod.dark_red
    monster.blocks = False
    monster.fighter = None
    monster.ai = None
    monster.name = 'remains of ' + monster.name
    send_to_back(monster)

def send_to_back(object):
    global objects
    objects.remove(object)
    objects.insert(0, object)

def render_bar(x, y, total_width, name, value, maximum, bar_color, back_color):
    global panel
    bar_width = int(float(value) / maximum * total_width)

    libtcod.console_set_default_background(panel, back_color)
    libtcod.console_rect(panel,x, y, total_width, 1, False, libtcod.BKGND_SCREEN)

    libtcod.console_set_default_background(panel, bar_color)
    if bar_width > 0:
        libtcod.console_rect(panel, x, y, bar_width, 1, False, libtcod.BKGND_SCREEN)

    libtcod.console_set_default_foreground(panel, libtcod.white)
    libtcod.console_print_ex(panel, x + total_width / 2, y, libtcod.BKGND_NONE, libtcod.CENTER, name + ': ' + str(value) + '/' + str(maximum))

def get_names_under_mouse():
    global mouse, fov_map
    # Get a string with the names of the objects under the mouse
    (x, y) = (mouse.cx, mouse.cy)
    names = [obj.name for obj in objects
            if obj.x == x and obj.y == y and libtcod.map_is_in_fov(fov_map, obj.x, obj.y)]
    names = ','.join(names)
    return names.capitalize()

def menu(header, options, width):
    if len(options) > 26:
        raise ValueError('Your inventory should not be greater than 26.')

    header_height = libtcod.console_get_height_rect(con, 0, 0, width, SCREEN_HEIGHT, header)
    height = len(options) + header_height

    # initialize the window to print to
    window = libtcod.console_new(width, height)
    libtcod.console_set_default_foreground(window, libtcod.white)
    libtcod.console_print_rect_ex(window, 0,0, width, height, libtcod.BKGND_NONE, libtcod.LEFT, header)

    # print the inventory
    y = header_height
    letter_index = ord('a')
    for option_text in options:
        text = '(' + chr(letter_index) + ') ' + option_text
        libtcod.console_print_ex(window, 0, y, libtcod.BKGND_NONE, libtcod.LEFT, text)
        y += 1
        letter_index += 1

    # applyTo the contents of "window" to the root console
    x = SCREEN_WIDTH /2 - width/2
    y = SCREEN_HEIGHT/2 - height/2
    libtcod.console_blit(window, 0,0,width, height, 0, x, y, 1.0, 0.7)

    # present the root console to the player and wait for a key-press
    libtcod.console_flush()
    key = libtcod.console_wait_for_keypress(True)

    # convert the ASCII code to an index
    index = key.c - ord('a')
    if index >= 0 and index  < len(options):
        return index
    return None

def inventory_menu(header):
    global inventory
    # show a menu with each item of the inventory as an option
    if len(inventory) == 0:
        options = ['Inventory is empty.']
    else:
        options = [item.name for item in inventory]

    index = menu(header, options, INVENTORY_WIDTH)

    if index is None or len(inventory) == 0:
        return None
    return inventory[index].item

def cast_heal():
    #heal the player
    message('Your wounds start to feel better!', libtcod.light_violet)
    player.fighter.heal(HEAL_AMOUNT)


###################
# Start the game! #
###################
#
# Initialization stuff
##

# Include a custom font
libtcod.console_set_custom_font('arial10x10.png', libtcod.FONT_TYPE_GREYSCALE | libtcod.FONT_LAYOUT_TCOD)

# Initialize the window (widthl, height, title, isFullscreen)
libtcod.console_init_root(SCREEN_WIDTH, SCREEN_HEIGHT, 'Eldritch Dungeons', False)

# Limit the frames per second for a real-time roguelike
libtcod.sys_set_fps(LIMIT_FPS)

# Create a new console for graphics buffering
con = libtcod.console_new(SCREEN_WIDTH, SCREEN_HEIGHT)

# make the panel
panel = libtcod.console_new(SCREEN_WIDTH, PANEL_HEIGHT)


# Create the player
fighter_component = Fighter(hp=30, defense=2, power=5, death_function = player_death)
player = Object(PLAYER_X, PLAYER_Y, '@', 'William Dyer', libtcod.white, True, fighter=fighter_component)
player.attributes["fighter"] = fighter_component
player.attributes[fire.ATRIBUTE_FLAMABLE_KEY] = fire.attribute_flammable(player, flame_resistance=60)

# A list of the characters in the game
objects = [player]

#initialize the map
map = None
makeMap()

fov_map = libtcod.map_new(MAP_WIDTH, MAP_HEIGHT)
for y in range(MAP_HEIGHT):
    for x in range(MAP_WIDTH):
        libtcod.map_set_properties(fov_map, x, y, not map[x][y].blockedSight, not map[x][y].blocked)

message('Welcome stranger! Prepare to die in the Eldritch Dungeons')
##
# Run the game!
##

while not libtcod.console_is_window_closed():
    # Set the color of the text
    # First param: 0 specifies console (the screen)
    #              con specifies the console buffer
    libtcod.console_set_default_foreground(con, libtcod.white)

    libtcod.sys_check_for_event(libtcod.EVENT_KEY_PRESS | libtcod.EVENT_MOUSE, key, mouse)

    # Draw all objects on the screen
    renderAll()

    # Push the changes to the screen
    # This is handled before the key presses so that the
    # first screen of the game is not blank
    libtcod.console_flush()

    # Clear all objects on the screen
    if not isFirstTurn:
        for obj in objects:
            obj.clear(con)
    else:
        isFirstTurn = False

    # Handle the keys and exit the game if needed
    player_action = handleKeys()
    if player_action == 'exit':
        break

    # Let the monsters take their turns
    if game_state == 'playing' and player_action != 'didnt-take-turn':
        for object in objects:
            if object != player and object.ai:
                object.ai.take_turn(fov_map, map, objects, player)

        player.draw(con, fov_map)



